package com.syj.qdp.framework.apilog.core.filter;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.syj.qdp.framework.common.exception.enums.GlobalErrorCodeConstants;
import com.syj.qdp.framework.common.pojo.CommonResult;
import com.syj.qdp.framework.common.util.date.DateUtils;
import com.syj.qdp.framework.common.util.json.JsonUtils;
import com.syj.qdp.framework.common.util.monitor.TracerUtils;
import com.syj.qdp.framework.common.util.servlet.ServletUtils;
import com.syj.qdp.framework.apilog.config.ApiLogProperties;
import com.syj.qdp.framework.web.config.WebProperties;
import com.syj.qdp.framework.web.util.WebFrameworkUtils;
import com.syj.qdp.framework.apilog.core.service.ApiAccessLogFrameworkService;
import com.syj.qdp.framework.apilog.core.service.dto.ApiAccessLogCreateDTO;
import lombok.RequiredArgsConstructor;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.Map;

/**
 * 每个Request请求记录访问日志
 *
 * @author Lyon
 */
@RequiredArgsConstructor
public class ApiAccessLogFilter extends OncePerRequestFilter {

    final ApiAccessLogFrameworkService accessLogFrameworkService;
    final WebProperties webProperties;
    final ApiLogProperties apiLogProperties;
    final String applicationName;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        Date beginTime = new Date();
        String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtil.getBody(request) : null;
        Map<String, String> queryParams = ServletUtil.getParamMap(request);
        try {
            chain.doFilter(request, response);
            createApiAccessLog(beginTime, requestBody, queryParams, request, response, null);
        } catch (Exception ex) {
            createApiAccessLog(beginTime, requestBody, queryParams, request, response, ex);
            throw ex;
        }

    }

    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        return !apiLogProperties.isEnable() || !request.getRequestURI().startsWith(webProperties.getApiPrefix());
    }

    /**
     * 创建请求访问日志
     * @param beginTime 请求开始时间
     * @param requestBody 请求body数据
     * @param queryParams 请求query数据
     * @param request 请求
     * @param response 响应
     * @param ex 异常
     */
    protected void createApiAccessLog(Date beginTime, String requestBody, Map<String, String> queryParams, HttpServletRequest request, HttpServletResponse response, Exception ex) {
        ApiAccessLogCreateDTO apiAccessLogCreateDTO = new ApiAccessLogCreateDTO();
        // 填充请求信息
        fillReuestDetail(apiAccessLogCreateDTO, request, requestBody, queryParams);
        // 填充用户信息
        fillUserDetail(apiAccessLogCreateDTO, request);
        // 填充返回信息
        fillResultDetail(apiAccessLogCreateDTO, request, response, ex);
        // 填充耗时信息
        fillDuration(apiAccessLogCreateDTO, beginTime);
        accessLogFrameworkService.createApiAccessLogAsync(apiAccessLogCreateDTO);
    }

    /**
     * 填充请求耗时信息
     *
     * @param apiAccessLogCreateDTO 日志访问对象
     * @param beginTime             请求开始时间
     */
    private void fillDuration(ApiAccessLogCreateDTO apiAccessLogCreateDTO, Date beginTime) {
        apiAccessLogCreateDTO.setBeginTime(beginTime);
        Date endTime = new Date();
        apiAccessLogCreateDTO.setEndTime(endTime);
        apiAccessLogCreateDTO.setDuration((int) DateUtils.diff(endTime, beginTime));
    }

    /**
     * 填充请求返回信息
     *
     * @param apiAccessLogCreateDTO 访问日志对象
     * @param request               请求
     * @param response              请求回应
     * @param ex                    异常
     */
    private void fillResultDetail(ApiAccessLogCreateDTO apiAccessLogCreateDTO, HttpServletRequest request, HttpServletResponse response, Exception ex) {
        Integer resultCode;
        String resultMsg;
        CommonResult<?> commonResult = WebFrameworkUtils.getCommonResult(request);
        if (commonResult != null) {
            resultCode = commonResult.getCode();
            resultMsg = commonResult.getMsg();
        } else if (ex != null) {
            resultCode = GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode();
            resultMsg = ExceptionUtil.getRootCauseMessage(ex);
        } else {
            resultCode = GlobalErrorCodeConstants.SUCCESS.getCode();
            resultMsg = null;
        }
        apiAccessLogCreateDTO
                .setResultCode(resultCode)
                .setResultMsg(resultMsg);

    }

    /**
     * 填充用户信息
     *
     * @param apiAccessLogCreateDTO 访问日志对象
     * @param request               请求
     */
    private void fillUserDetail(ApiAccessLogCreateDTO apiAccessLogCreateDTO, HttpServletRequest request) {
        apiAccessLogCreateDTO
                .setUserId(WebFrameworkUtils.getLoginUserId() + "")
                .setUserType(WebFrameworkUtils.getUserType(request))
                .setUserAgent(ServletUtils.getUserAgent(request))
                .setUserIp(ServletUtils.getClientIP());
    }

    /**
     * 填充请求信息
     *
     * @param apiAccessLogCreateDTO 访问日志对象
     * @param request               请求
     * @param requestBody
     * @param queryParams
     */
    private void fillReuestDetail(ApiAccessLogCreateDTO apiAccessLogCreateDTO, HttpServletRequest request, String requestBody, Map<String, String> queryParams) {
        Map<String, Object> params = MapUtil.<String, Object>builder().put("query", queryParams).put("body", requestBody).build();
        apiAccessLogCreateDTO
                .setApplicationName(applicationName)
                .setTraceId(TracerUtils.getTraceId())
                .setRequestUrl(request.getRequestURI())
                .setRequestMethod(request.getMethod())
                .setRequestParams(JsonUtils.toJsonString(params));
    }
}
